﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Xml.Linq;
using WebHelper.Helpers;

namespace WebHelper.Handlers
{
    [Serializable]
    public class DownloadHandler:IHttpHandler
    {
        #region Static Implementation

        public static string DefaultPath
        {
            get
            {
                return "Download.axd";
            }
        }
        public static string FileName
        {
            get
            {
                return "F";
            }
        }
        public static string Url
        {
            get
            {
                return "U";
            }
        }
        public static string Register(bool writeToWebConfig, string filePath)
        {
            string path = DownloadHandler.DefaultPath;
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement httpHandlers = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpHandlers");
            var configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
                writeToWebConfig = false;
            }
            XElement handlers = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "handlers");
            configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
                writeToWebConfig = false;
            }
            //iis 6
            configElement = httpHandlers.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(DownloadHandler).FullName));
            XElement element = new XElement("add",
                new XAttribute("path", path),
                new XAttribute("type", typeof(DownloadHandler).FullName + "," + typeof(DownloadHandler).Assembly.FullName),
                new XAttribute("verb", "*"),
                new XAttribute("validate", "false"));
            httpHandlers.Add(element);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in system.web", typeof(DownloadHandler).Name + "  ");

            //iis7
            configElement = handlers.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement == null)
            {
                if (handlers.Elements("remove").Count() > 0)
                {
                    handlers.Elements("remove").Last().AddAfterSelf(new XElement("remove", new XAttribute("name", typeof(DownloadHandler).FullName)));
                }
                else
                {
                    handlers.AddFirst(new XElement("remove", new XAttribute("name", typeof(DownloadHandler).FullName)));
                }
            }
            element = new XElement("add",
                new XAttribute("name", typeof(DownloadHandler).FullName),
                new XAttribute("path", path),
                new XAttribute("type", typeof(DownloadHandler).FullName + "," + typeof(DownloadHandler).Assembly.FullName),
                new XAttribute("verb", "*"),
                new XAttribute("preCondition", "integratedMode"));
            handlers.Add(element);


            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in system.webServer", typeof(DownloadHandler).Name + "  ");


            if (writeToWebConfig)
            {
                document.Save(filePath);

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register and saved at" + filePath, typeof(DownloadHandler).Name + "  ");

            }
            return element.ToString();
        }
        public static void UnRegister(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement configElement = null;
            XElement httpHandlers = XmlHelper.GetElementsOfPath(document.Root, "system.web", "httpHandlers").FirstOrDefault();
            if (httpHandlers != null)
            {
                configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            XElement handlers = XmlHelper.GetElementsOfPath(document.Root, "system.webServer", "handlers").FirstOrDefault();
            if (handlers != null)
            {
                configElement = handlers.Elements("remove").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(DownloadHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
                configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
                if (configElement != null)
                {
                    configElement.Remove();
                }
            }
            document.Save(filePath);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "unRegister from web.Congig", typeof(DownloadHandler).Name + "  ");

        }

        public static string Download(Uri uri, string fileName,bool autoDownload)
        {
            string encodedUrl = DownloadHandler.Url +"="+ HexaDecimalHelper.Encode(Encoding.UTF8.GetBytes(uri.AbsoluteUri)) + "&" + DownloadHandler.FileName + "=" + HexaDecimalHelper.Encode(Encoding.UTF8.GetBytes(fileName));

            string path = DownloadHandler.DefaultPath;
            XDocument document = XDocument.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            XElement httpHandlers = XmlHelper.CreateIfNotExist(document.Root, "system.web", "httpHandlers");
            var configElement = httpHandlers.Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
            }
            XElement handlers = XmlHelper.CreateIfNotExist(document.Root, "system.webServer", "handlers");
            configElement = handlers.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(DownloadHandler).FullName));
            if (configElement != null)
            {
                path = configElement.Attribute("path").Value;
            }

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Url = " + uri.AbsoluteUri + ", FileName = " + fileName + ", EncodedUrl = " + encodedUrl, typeof(DownloadHandler).Name + "  ");
            if (autoDownload)
            {
                HttpContext.Current.Response.Filter = new DownloadFilter(HttpContext.Current.Response.Filter, UrlHelper.GetCompleteUri("~/" + path + "?" + encodedUrl));
            }
            
            return UrlHelper.GetCompleteUri("~/" + path + "?" + encodedUrl);
        }
        public void ResponseStream(HttpContext context, Stream stream, string fileName, long fileLength)
        {
            context.Response.Clear();
            if (fileLength > 0)
            {
                using (BinaryReader br = new BinaryReader(stream))
                {
                    long startBytes = 0;

                    context.Response.AddHeader("Accept-Ranges", "bytes");
                    context.Response.Buffer = false;
                    int pack = 10240; //10K bytes
                    //int sleep = (int)Math.Floor((double)(1000 * pack / speed)) + 1;
                    if (context.Request.Headers["Range"] != null)
                    {
                        context.Response.StatusCode = 206;
                        string[] range = context.Request.Headers["Range"].Split(new char[] { '=', '-' });
                        startBytes = Convert.ToInt64(range[1]);
                    }
                    context.Response.AddHeader("Content-Length", (fileLength - startBytes).ToString());
                    if (startBytes != 0 && stream.CanSeek)
                    {
                        context.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength));
                    }
                    context.Response.AddHeader("Connection", "Keep-Alive");
                    context.Response.ContentType = "application/octet-stream";
                    context.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));
                    if (stream.CanSeek)
                    {
                        br.BaseStream.Seek(startBytes, SeekOrigin.Begin);
                    }
                    int maxCount = (int)Math.Floor((double)((fileLength - startBytes) / pack)) + 1;
                    for (int i = 0; i < maxCount; i++)
                    {
                        if (context.Response.IsClientConnected)
                        {
                            context.Response.BinaryWrite(br.ReadBytes(pack));
                            context.Response.Flush();
                            //Thread.Sleep(sleep);

                            //trace
                            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "resonse flush: no=" + i + ",pack=" + pack, typeof(DownloadHandler).Name + "  ");

                        }
                        else
                        {
                            i = maxCount;

                            //trace
                            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "client not conected", typeof(DownloadHandler).Name + "  ");
                        }
                    }
                }
                context.Response.End();
            }
        }

        #endregion


        public bool IsReusable
        {
            get { return true; }
        }
        public void ProcessRequest(HttpContext context)
        {
            string urlParam =Encoding.UTF8.GetString(HexaDecimalHelper.Decode(context.Request.QueryString[DownloadHandler.Url]));
            string FileNameParam = Encoding.UTF8.GetString(HexaDecimalHelper.Decode(context.Request.QueryString[DownloadHandler.FileName]));
            if (!string.IsNullOrEmpty(urlParam) && !string.IsNullOrEmpty(FileNameParam))
            {
                if (UrlHelper.IsInternalUri(urlParam))
                {
                    string resousrcePath = context.Server.MapPath(new Uri(urlParam).AbsolutePath);
                    FileInfo fi = new FileInfo(resousrcePath);
                    context.Response.Headers.Add("Last-Modified", fi.LastWriteTimeUtc.ToString("r"));
                    context.Response.Headers.Add("ETag", "\"DownLoad" + fi.LastWriteTimeUtc.ToString("yyyyMMddHHmmss") + "\"");
                    using (FileStream fs = new FileStream(resousrcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        long length = fs.Length;//for trace

                        this.ResponseStream(context, fs, FileNameParam, fs.Length);
                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Download internally(filestram):" + urlParam + ",filename=" + FileNameParam + ",length=" + length, typeof(DownloadHandler).Name + "  ");

                    }
                }
                else
                {
                    WebRequest request = WebRequest.Create(urlParam);
                    WebResponse response = request.GetResponse();
                    context.Response.Headers.Add("Last-Modified", response.Headers[HttpResponseHeader.LastModified]);
                    context.Response.Headers.Add("ETag", response.Headers[HttpResponseHeader.ETag]);
                    using (Stream str = response.GetResponseStream())
                    {
                        long length = response.ContentLength;//for trace

                        this.ResponseStream(context, str, FileNameParam, response.ContentLength);

                        //trace
                        Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Download Externally(responseStream):" + urlParam + ",filename=" + FileNameParam + ",length=" + length, typeof(DownloadHandler).Name + "  ");

                    }
                }
            }
            else
            {
                context.Response.StatusCode = 404;

                //trace
                Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "no data found", typeof(DownloadHandler).Name + "  ");

            }
        }
    }
}
